﻿// Copyright (c) Microsoft Corporation.  All rights reserved. 
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
// using WiE.Model.Sync;

namespace WiE.Model
{
    /// <summary>
    /// This class implements the Data Model for the application.  This model leverages both a local datastore for
    /// caching information and for offline availability and then uses a remote version of the model to save / publish
    /// the information to a remote server.
    /// </summary>
    class WiEModelWithLocalCache : IWiEModel
    {
        private WiEModelLocal m_localModel;
        private WiEModelRemoteSSDSForMobile m_remoteModel;
        private WiEModelSynch m_syncAgent;

        #region IWiEModel Members

        /// <summary>
        /// Event raised when the location history table has been updated (i.e. new item added / removed, etc.)
        /// </summary>
        public event EventHandler<LocationHistoryChangedArgs> RaiseLocationHistoryChangedEvent;

        /// <summary>
        /// Event raised when the device table has been updated (i.e. new item added / removed, etc.)
        /// </summary>
        public event EventHandler<DeviceChangedArgs> RaiseDeviceChangedEvent;

        /// <summary>
        /// Event raised when the member table has been updated (i.e. new item added / removed, etc.)
        /// </summary>
        public event EventHandler<MemberChangedArgs> RaiseMemberChangedEvent;

        /// <summary>
        /// Initialize the Model, creating a local cache and a remote model provider with a 
        /// synch object that keeps the two in synch where necessary.
        /// </summary>
        public void Initialize()
        {
            // Create the local model and initialize it
            m_localModel = new WiEModelLocal();
            m_localModel.Initialize();
            
            // Create the remote model and initialize it
            m_remoteModel = new WiEModelRemoteSSDSForMobile();
            m_remoteModel.Initialize();

            // Since this model is really a wrapper for the two real model, we should listen to events from 
            // the individual model implementations so that we can "bubble" them up to anyone that might care.
            m_localModel.RaiseLocationHistoryChangedEvent += new EventHandler<LocationHistoryChangedArgs>(OnLocationHistoryChangedEvent);
            m_localModel.RaiseDeviceChangedEvent += new EventHandler<DeviceChangedArgs>(OnDeviceChangedEvent);
            m_localModel.RaiseMemberChangedEvent += new EventHandler<MemberChangedArgs>(OnMemberChangedEvent);

            m_remoteModel.RaiseLocationHistoryChangedEvent += new EventHandler<LocationHistoryChangedArgs>(OnLocationHistoryChangedEvent);
            m_remoteModel.RaiseDeviceChangedEvent +=new EventHandler<DeviceChangedArgs>(OnDeviceChangedEvent);
            m_remoteModel.RaiseMemberChangedEvent +=new EventHandler<MemberChangedArgs>(OnMemberChangedEvent);

            // Create/Initiate the synch agent that will keep Local cache and Remote Model "in synch", the synch agent
            // leverages the events from the model to trigger synch actions (in addition to any background processing the
            // agent also supports).
            m_syncAgent = new WiEModelSynch();
            m_syncAgent.LocalModel = m_localModel;
            m_syncAgent.RemoteModel = m_remoteModel;
            m_syncAgent.Initialize();
        }

        /// <summary>
        /// Event handler for the MemberChangedEvent events from the local and remote model
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnMemberChangedEvent(object sender, MemberChangedArgs e)
        {
            // Member was changed on either the local or the remote model, let anyone that cares know...
            if (RaiseMemberChangedEvent != null)
                RaiseMemberChangedEvent(this, e);
        }

        /// <summary>
        /// Event handler for DeviceChangedEvent events from the local and remote models (only expected from local model)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnDeviceChangedEvent(object sender, DeviceChangedArgs e)
        {
            // Device was changed on either the local or the remote model, let anyone that cares know...
            if (RaiseDeviceChangedEvent != null)
                    RaiseDeviceChangedEvent(this, e);
        }

        /// <summary>
        /// Event handler for LocationHistoryChanged events from the local and remote models (expected from local model
        /// only).
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnLocationHistoryChangedEvent(object sender, LocationHistoryChangedArgs e)
        {
            if (RaiseLocationHistoryChangedEvent != null)
                RaiseLocationHistoryChangedEvent(this, e);
        }

        /// <summary>
        /// Application is informing us that it no longet needs the model (i.e. may be exiting), so free up any
        /// resources we no longer need by informing our components (local model, remote model and synch agent) that
        /// we are going away.
        /// </summary>
        public void Terminate()
        {
            if (m_syncAgent != null)
            {
                m_syncAgent.Terminate();
                m_syncAgent = null;
            }

            if (m_localModel != null)
            {
                m_localModel.Terminate();
            }

            if (m_remoteModel != null)
            {
                m_remoteModel.Terminate();
                m_remoteModel = null;
            }
        }

        /// <summary>
        /// Retrieve the specified location object (likely from remote server)
        /// </summary>
        /// <param name="p_guidLocationID"></param>
        /// <returns></returns>
        public WiELocationDataObject GetLocation(Guid p_guidLocationID)
        {
            WiELocationDataObject locationObject = null;

            // Attempt to retrieve from local first
            locationObject = m_localModel.GetLocation(p_guidLocationID);

            if (locationObject == null)
            {
                // The location object was not available locally (it was most likely already synched and deleted)
                // try to retrieve it from the remote data store instead
                locationObject = m_remoteModel.GetLocation(p_guidLocationID);
            }

            return (locationObject);
        }

        /// <summary>
        /// Removes the specificed location from the datastore (local and remote).  Use carefully.
        /// </summary>
        /// <param name="p_guidLocationID"></param>
        public void RemoveLocation(Guid p_guidLocationID)
        {
            // Remove the location from the local model / data store...
            m_localModel.RemoveLocation(p_guidLocationID);

            try
            {
                // Future: The synch mechanism should propagate the delete (if it makes sense to do so)
                // For now we go ahead and attempt to delete.
                m_remoteModel.RemoveLocation(p_guidLocationID);
            }
            catch (Exception remoteRemoveLocationException)
            {
                System.Diagnostics.Trace.WriteLine("Unable to remove the location from the remote data store. Exception: " + remoteRemoveLocationException.ToString());
            }
        }

        /// <summary>
        /// Retrieves the location history for this device from the remote datastore if available.
        /// </summary>
        /// <param name="p_dtStartTimeUTC"></param>
        /// <param name="p_dtStopTimeUTC"></param>
        /// <returns></returns>
        public List<WiELocationDataObject> GetLocationHistoryBetween(DateTime p_dtStartTimeUTC, DateTime p_dtStopTimeUTC)
        {
            List<WiELocationDataObject> locationHistory = m_localModel.GetLocationHistoryBetween(p_dtStartTimeUTC, p_dtStopTimeUTC);

            // If we have no longer have locally stored location history, we try to get it from the remote store.
            if (locationHistory.Count == 0)
            {
                locationHistory = m_remoteModel.GetLocationHistoryBetween(p_dtStartTimeUTC, p_dtStopTimeUTC);
            }

            return (locationHistory);
        }

        /// <summary>
        /// Retrieve the location history for this device from the datastore if available.
        /// </summary>
        /// <param name="p_dtEndTimeUTC"></param>
        /// <returns></returns>
        public List<WiELocationDataObject> GetLocationHistoryBefore(DateTime p_dtEndTimeUTC)
        {
            List<WiELocationDataObject> locationHistory = m_localModel.GetLocationHistoryBefore(p_dtEndTimeUTC);

            // If we have no longer have locally stored location history, we try to get it from the remote store.
            if (locationHistory.Count == 0)
            {
                locationHistory = m_remoteModel.GetLocationHistoryBefore(p_dtEndTimeUTC);
            }

            return (locationHistory);
        }

        /// <summary>
        /// Save the location to the local cache datastore (and asynchronously to remote datastore through synchronization)
        /// </summary>
        /// <param name="p_locationDataObject"></param>
        public void SaveLocation(WiELocationDataObject p_locationDataObject)
        {
            // Save to local data store, sync makes sure to get it over to the remote data store.
            m_localModel.SaveLocation(p_locationDataObject);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="p_memberDataObject"></param>
        public void SaveMember(WiEMemberDataObject p_memberDataObject)
        {
            // Save the member (insert or update) in the local database and it will be synchronized
            // to the remote store.
            m_localModel.SaveMember(p_memberDataObject);
        }

        /// <summary>
        /// Retrieves the member specified by p_guidMemberID.  It first attempts to read the member from the remote data store as it is viewed
        /// as the "master" for member records.  If the remote is unavailable (or if the record does not exist), the local data store is queried
        /// to get cached version.
        /// </summary>
        /// <param name="p_guidMemberID"></param>
        /// <returns></returns>
        public WiEMemberDataObject GetMember(Guid p_guidMemberID)
        {
            WiEMemberDataObject memberDataObject = null;

            try
            {
                // Attempt to retrieve the Member information from remote (remote is assumed to always be the best "source")
                memberDataObject = m_remoteModel.GetMember(p_guidMemberID);
            }
            catch (Exception remoteGetMemberException)
            {
                System.Diagnostics.Trace.WriteLine("GetMember() could not connect or retrieve member from remote model, trying to retrieve from local model: " + remoteGetMemberException.ToString());
            }

            // Did we retrieve the member?
            if (null != memberDataObject)
            {
                // We retrieved the member information from the remote store, save / update locally
                // to have a cached version for use will disconnected.
                m_localModel.SaveMember(memberDataObject);
            }
            else
            {
                // We we unable to retrieve the member from the remote data store, so we try to retrieve it from the local datastore.
                memberDataObject = m_localModel.GetMember(p_guidMemberID);
            }

            // Return the results (if any)...
            return (memberDataObject);
        }

        /// <summary>
        /// Retrieves the member associated with the username.  Tries the remote datastore first (as it is considered
        /// the owner / trusted authentication source of the data) and uses cached local data if the remote is inaccessible.
        /// </summary>
        /// <param name="p_strUserName"></param>
        /// <returns></returns>
        public WiEMemberDataObject GetMemberByUserName(string p_strUserName)
        {
            WiEMemberDataObject memberDataObject = null;

            try
            {
                // Attempt to retrieve the Member information from remote (remote is assumed to always be the best "source")
                memberDataObject = m_remoteModel.GetMemberByUserName(p_strUserName);
            }
            catch (Exception remoteGetMemberException)
            {
                System.Diagnostics.Trace.WriteLine("GetMember() could not connect or retrieve member from remote model, trying to retrieve from local model: " + remoteGetMemberException.ToString());
            }

            if (null != memberDataObject)
            {
                // Update the local copy with the latest version from the remote model
                m_localModel.SaveMember(memberDataObject);
            }
            else
            {
                // We we unable to retrieve the member from the remote data store, so we try to retrieve it from the local datastore.
                memberDataObject = m_localModel.GetMemberByUserName(p_strUserName);
            }

            return (memberDataObject);
        }

        /// <summary>
        /// Retrieves the member associated with the phone number p_strPhoneNumber, it first tries the remote data store
        /// and uses cached version if remote data store is unavailable.
        /// </summary>
        /// <param name="p_strPhoneNumber"></param>
        /// <returns></returns>
        public WiEMemberDataObject GetMemberByPhoneNumber(string p_strPhoneNumber)
        {
            WiEMemberDataObject memberDataObject = null;

            try
            {
                // Attempt to retrieve the Member information from remote (remote is assumed to always be the best "source")
                memberDataObject = m_remoteModel.GetMemberByPhoneNumber(p_strPhoneNumber);
            }
            catch (Exception remoteGetMemberException)
            {
                System.Diagnostics.Trace.WriteLine("GetMember() could not connect or retrieve member from remote model, trying to retrieve from local model: " + remoteGetMemberException.ToString());
            }

            if (null != memberDataObject)
            {
                // Update the local copy with the latest version from the remote model
                m_localModel.SaveMember(memberDataObject);
            }
            else
            {
                // We we unable to retrieve the member from the remote data store, so we try to retrieve it from the local datastore.
                memberDataObject = m_localModel.GetMemberByPhoneNumber(p_strPhoneNumber);
            }

            return (memberDataObject);
        }

        /// <summary>
        /// Saves the device to the local and remote data stores.
        /// </summary>
        /// <param name="p_deviceDataObject"></param>
        public void SaveDevice(WiEDeviceDataObject p_deviceDataObject)
        {
            // We save the device in the local database, the sync process will save it 
            // to the remote store.
            m_localModel.SaveDevice(p_deviceDataObject);
        }

        /// <summary>
        /// Retrieves the device data object associated with p_guidDeviceID
        /// </summary>
        /// <param name="p_guidDeviceID"></param>
        /// <returns></returns>
        public WiEDeviceDataObject GetDevice(Guid p_guidDeviceID)
        {
            // Retrieve the device data object from the local data source (as it is the "master" of device information).
            return m_localModel.GetDevice(p_guidDeviceID);
        }

        /// <summary>
        /// Retrieves the list of devices associated with member p_guidMemberID (expected to be one in local model).
        /// </summary>
        /// <param name="p_guidMemberID"></param>
        /// <returns></returns>
        public List<WiEDeviceDataObject> GetDevicesByMember(Guid p_guidMemberID)
        {
            // Retrieve the device data object from the local data source (as it is the "master" of its domain).
            // This may need to be revised if we want to get a list of all the member's devices.
            return m_localModel.GetDevicesByMember(p_guidMemberID);
        }

        /// <summary>
        /// Returns true if the model is able to handle requests, which for this model means at a minimum ability to 
        /// access and store within the local model / cache.
        /// </summary>
        /// <returns></returns>
        public bool IsAvailable()
        {
            return m_localModel.IsAvailable();
        }

        /// <summary>
        /// Returns all members currently stored / cached in the local data store.
        /// </summary>
        /// <returns></returns>
        public List<WiEMemberDataObject> GetMembers()
        {
            return m_localModel.GetMembers();
        }

        /// <summary>
        /// Returns all devices currently stored / cached in the local data store.
        /// </summary>
        /// <returns></returns>
        public List<WiEDeviceDataObject> GetDevices()
        {
            return m_localModel.GetDevices();
        }

        #endregion
    }
}
